#pragma once

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <functional>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/wait.h>
#include <pthread.h>

#include "log.hpp"
#include "Inetaddr.hpp"
#include "threadpool.hpp"

using namespace log_namespace;

enum
{
    SOCKET_ERROR = 1
    ,BIND_ERROR
    ,LISTEN_ERROR
};

const static int gport = 8888;
const static int gsock = -1;
const static int gbacklog = 8;

using task_t = std::function<void()>;

class tcpServer
{    
public:
    tcpServer(uint16_t port = gport)
        :_port(gport)
        ,_listensocketfd(gsock)
        ,_isrunning(false)
    {
        
    }

    void initServer()
    {
        _listensocketfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensocketfd < 0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL, "socket create error\n");
        }
        LOG(INFO, "socket create success, socketfd: %d\n", _listensocketfd);

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        // 绑定本主机的套接字和 对方主机的地址和端口号
        if(::bind(_listensocketfd, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            LOG(FATAL, "bind error!\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

        // tcp是面向连接的，tcp需要未来不断地获取链接
        if (::listen(_listensocketfd, gbacklog) < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen success\n");

    }

    //version 0: 不能实现多个客户端同时连接服务器
    // void loop()
    // {
    //     _isrunning = true;
    //     while (_isrunning)
    //     {
    //         struct sockaddr_in client;
    //         socklen_t len = sizeof(client);
    //         // 获取新连接
    //         // accept 这个函数利用 _socketfd建立好的链接，
    //         // 创建一个能够处理任务的文件，并返回这个文件的文件描述符
    //         int socketfd = accept(_listensocketfd, (struct sockaddr*)&client, &len);
    //         if (socketfd < 0)
    //         {
    //             LOG(WARNING, "accept error\n");
    //             continue;
    //         }
    //         inetAddr addr(client);
    //         LOG(INFO, "get a new link, client info: %s\n, socketfd is: %d\n", addr.addrStr().c_str(), socketfd);
    //         
    //         service(socketfd, addr);
    //     }
    //     _isrunning = false;
    // }

    // version 1: 多进程版本
    // void loop()
    // {
    //     //为了保证两点
    //     //  1. 子进程在处理任务的时候父进程也可以处理任务
    //     //  2. 子进程一旦处理完任务，父进程就会对其进行回收，不出现僵尸进程的情况
    //     // 可以采取三种方式：
    //     // 方式一：使用signal函数对SIGCHLD这个信号进行忽略，
    //     // signal(SIGCHLD, SIG_IGN);
    //     _isrunning = true;
    //     while (_isrunning)
    //     {
    //         struct sockaddr_in client;
    //         socklen_t len = sizeof(client);
    //         // 获取新连接
    //         // accept 这个函数利用 _socketfd建立好的链接，
    //         // 创建一个能够处理任务的件，并返回这个文件的文件描述符
    //         int socketfd = accept(_listensocketfd, (struct sockaddr*)&client, &len);
    //         if (socketfd < 0)
    //         {
    //             LOG(WARNING, "accept error\n");
    //             continue;
    //         }
    //         inetAddr addr(client);
    //         LOG(INFO, "get a new link, client info: %s\n, socketfd is: %d\n", addr.addrStr().c_str(), socketfd);
    //         // service(socketfd, addr);        
    //         pid_t id = fork();
    //         if (id == 0)
    //         {
    //             // if代码块内部是子进程
    //             // 子进程会继承父进程的文件描述符表，即父子进程有两张同样的文件指针数组
    //             // 所以父子进程所指向的文件是一致的
    //             // 那么在没有关闭任何文件之前，子进程和父进程都将包括两个文件描述符
    //             // 它们分别是 _listensocketfd 和 socketfd
    //             // 实际上我们设计时只希望 父进程只拥有_listensocketfd，以便创建新的子进程
    //             // 子进程只拥有socketfd，以便子进程处理任务
    //             // "只拥有" 的目的有两个
    //             // 1. 进程只执行自己的代码可以防止误操作
    //             // 2. 每个进程拥有的文件指针数组的长度是有上限的，如果不及时关闭，
    //             //    可能会造成文件描述符泄漏问题
    //             ::close(_listensocketfd);
    //             // 方式二：
    //             // 在子进程中再创建一个 孙子进程
    //             // 实现时将会由孙子进程作为执行 service(socketfd, addr); 这句话的进程
    //             // 而且下面的判断会直接让子进程退出，这样孙子进程就变成了孤儿进程
    //             // 自然会被系统领养，那么就不会出现僵尸进程的情况了
    //             if(fork() > 0)
    //             {
    //                 exit(0);
    //             }
    //             service(socketfd, addr);
    //             exit(0);
    //         }

    //         // 之后是父进程
    //         // 下面在父进程中要等待子进程结束，以防子进程退出之后进入僵尸状态
    //         ::close(socketfd);

    //         // 方式三：
    //         // 将waitpid 中的最后一个参数设置为：非阻塞 且 轮询的方式进行等待
    //         int n = waitpid(id, nullptr, 0);
    //         if (n > 0)
    //         {
    //             LOG(INFO, "wait child success.\n");
    //         }
            
    //     }
    //     _isrunning = false;
    // }

    // version 2: 多线程版本
    // class threadData
    // {
    // public:
    //     threadData(int socketfd, tcpServer *tcS, const inetAddr &addr)
    //         :_ssocketfd(socketfd)
    //         ,_self(tcS)
    //         ,_addr(addr)
    //     {

    //     }
    //     int _ssocketfd;
    //     tcpServer *_self;
    //     inetAddr _addr;
    // };

    // static void *execute(void* args)
    // {
    //     threadData *td = static_cast<threadData *>(args);
    //     pthread_detach(pthread_self());
    //     td->_self->service(td->_ssocketfd, td->_addr);

    //     delete td;
    //     return nullptr;
    // }
    //
    // 子线程和主线程 共享一个 文件指针数组，所以不能通过随意关闭任意一个文件描述符
    // void loop()
    // {
    //     _isrunning = true;
    //     while (_isrunning)
    //     {
    //         struct sockaddr_in client;
    //         socklen_t len = sizeof(client);
    //         // 获取新连接
    //         // accept 这个函数利用 _socketfd建立好的链接，
    //         // 创建一个能够处理任务的件，并返回这个文件的文件描述符
    //         int socketfd = accept(_listensocketfd, (struct sockaddr*)&client, &len);
    //         if (socketfd < 0)
    //         {
    //             LOG(WARNING, "accept error\n");
    //             continue;
    //         }
    //         inetAddr addr(client);
    //         LOG(INFO, "get a new link, client info: %s\n, socketfd is: %d\n", addr.addrStr().c_str(), socketfd);

    //         pthread_t tid;
    //         threadData *td = new threadData(socketfd, this, addr);
    //         pthread_create(&tid, nullptr, execute, td);
    //     }
    //     _isrunning = false;
    // }

    // version 3: 线程池版本
    void loop()
    {
        _isrunning = true;
        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 获取新连接
            // accept 这个函数利用 _socketfd建立好的链接，
            // 创建一个能够处理任务的件，并返回这个文件的文件描述符
            int socketfd = accept(_listensocketfd, (struct sockaddr*)&client, &len);
            if (socketfd < 0)
            {
                LOG(WARNING, "accept error\n");
                continue;
            }
            inetAddr addr(client);
            LOG(INFO, "get a new link, client info: %s\n, socketfd is: %d\n", addr.addrStr().c_str(), socketfd);
            
            task_t t = std::bind(&tcpServer::service, this, socketfd, addr);
            threadPool<task_t>::getInstance()->enqueue(t);
        }
        _isrunning = false;
    }

    void service(int socketfd, inetAddr addr)
    {
        // 如果按照如下设计 服务，那么一旦客户端连上了服务器
        // 只要客户端没有退出，服务器端就会一直为这个客户端提供服务
        // 这种服务称为长服务
        while (true)
        {
            char inbuffer[1024];
            ssize_t n = ::read(socketfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                LOG(INFO, "get message from client %s, message: %s\n", addr.addrStr().c_str(), inbuffer);
                std::string echo_string = "[server echo]# ";
                echo_string += inbuffer;
                write(socketfd, echo_string.c_str(), echo_string.size());
            }
            else if(n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.addrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error: %s\n", addr.addrStr().c_str());
                break;
            }
        }
        ::close(socketfd);
    }

    ~tcpServer()
    {

    }
private:
    uint16_t _port;
    int _listensocketfd;
    bool _isrunning;
};


